package com.invest.amplegest.core;

import java.awt.Graphics2D;
import java.awt.SplashScreen;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.invest.amplegest.debug.Debug;
import com.invest.amplegest.exception.AldoException;
import com.invest.amplegest.other.GlobalConstant;
import com.invest.amplegest.xml.XMLUtil;
import com.invest.amplegest.xml.bean.AldoField;
import com.invest.amplegest.xml.bean.AldoLine;

/**
 * Conversion XLS vers ALDO.
 * 
 * @author Jean-François Augé
 * 30 juil. 2014
 */
public class AldoParser {

	// date format
	private static final SimpleDateFormat dayFormat = new SimpleDateFormat("YYYYMMdd");
	private static final SimpleDateFormat hourFormat = new SimpleDateFormat("hhmmss");

	// utilisé pour l'identifiant unique
	private int randomId = 0;

	// constant
	private static final int ID_INVEST = 475;
	private static final int LINE_LENGHT = 1536;

	// liste des lignes ALDO
	private List<String> aldoLines = new ArrayList<String>();

	// repertoire ou ce toruve le fichier XLS
	private String xlsDirPath = "";

	// timer
	private long begin = 0;


	/**
	 * Constructeur.
	 * Initialise le générateur aléatoire pour le compteur unique.
	 */
	public AldoParser() {
		Random rng = new Random(System.currentTimeMillis());
		randomId = rng.nextInt(90000);
	}

	/**
	 * Constructeur.
	 * Initialise le générateur aléatoire pour le compteur unique.
	 * 
	 * @param xlsPath chemin du fichier XLS
	 */
	public AldoParser(String xlsPath) {
		Random rng = new Random(System.currentTimeMillis());
		randomId = rng.nextInt(90000);

		String xlsAbsolutePath = new File(xlsPath).getAbsolutePath();
		this.xlsDirPath = new File(xlsAbsolutePath).getParentFile().getAbsolutePath();
	}


	/**
	 * Parse un fichier XLS passé en paramètre pour créer les lignes au format Aldo correspondantes.
	 * 
	 * @param aldoLineModel, la structure Aldo à remplir
	 * @param xlsFile, le fichier excel à parser
	 * @param sheetName, le nom de la feuille contenant les données Aldo dans le fichier excel
	 */
	public void createLinesFormXLS(AldoLine aldoLineModel, String xlsFile, String sheetName) {
		FileInputStream file = null;
		//HSSFWorkbook workbook = null;
		XSSFWorkbook workbook = null;

		try {
			file = new FileInputStream(new File(xlsFile));
			//workbook = new HSSFWorkbook(file); // get the workbook instance for XLS file
			workbook = new XSSFWorkbook(file); // get the workbook instance for XLSX and XLSM files
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		// get first sheet from the workbook
		//HSSFSheet sheet = workbook.getSheet(sheetName);
		XSSFSheet sheet = workbook.getSheet(sheetName);

		// get iterator to all the rows in current sheet
		Iterator<Row> rowIterator = sheet.iterator();

		int cpt = 0;
		// ignorer les 2 premieres lignes
		rowIterator.next();
		rowIterator.next();
		while(rowIterator.hasNext()) {
			begin = System.currentTimeMillis();

			Row row = rowIterator.next();

			// variables
			String sens = new String();
			Date date = null;
			String quantite = new String();
			String isin = new String();
			String libelleVal = new String();
			String numCompte = new String();
			String libelleCompte = new String();
			String donneur = new String();
			String service = new String();
			Date denouement = null;
			String cours = new String();
			String courtage = new String();
			String validation = new String();


			// parsing du fichier xml
			// sens : achat ou vente
			Cell cell0 = row.getCell(0);
			if(cell0 != null) {
				if(!cell0.getStringCellValue().equals(""))
					sens = cell0.getStringCellValue();
				else
					break;
			}
			else
				break; // on considère que si la première case est vide, la ligne vide et que le fichier a été entièrement lu

			// date
			Cell cell1 = row.getCell(1);
			if(cell1 != null)
				date = cell1.getDateCellValue();

			// quantité
			Cell cell2 = row.getCell(2);
			if(cell2 != null)
				quantite = String.valueOf((int) cell2.getNumericCellValue());

			// isin
			Cell cell3 = row.getCell(3);
			if(cell3 != null)
				isin = cell3.getStringCellValue();

			// libellé valeur
			Cell cell4 = row.getCell(4);
			if(cell4 != null)
				libelleVal = cell4.getStringCellValue();

			// numero de compte
			Cell cell5 = row.getCell(5);
			if(cell5 != null)
				numCompte = String.valueOf((int) cell5.getNumericCellValue());

			// libellé compte
			Cell cell6 = row.getCell(6);
			if(cell6 != null)
				libelleCompte = cell6.getStringCellValue();

			// donneur ordre
			Cell cell7 = row.getCell(7);
			if(cell7 != null)
				donneur = cell7.getStringCellValue();

			// service
			Cell cell8 = row.getCell(8);
			if(cell8 != null)
				service = String.valueOf((int) cell8.getNumericCellValue());

			// denouement
			Cell cell9 = row.getCell(9);
			if(cell9 != null)
				denouement = cell9.getDateCellValue();

			// cours
			Cell cell10 = row.getCell(10);
			if(cell10 != null)
				cours = String.valueOf(cell10.getNumericCellValue());

			// courtage
			Cell cell11 = row.getCell(11);
			if(cell11 != null) {
				if(cell11.getCellType() == Cell.CELL_TYPE_NUMERIC)
					courtage = String.valueOf(cell11.getNumericCellValue());
				else
					courtage = cell11.getStringCellValue();
			}

			// validation
			Cell cell12 = row.getCell(12);
			if(cell12 != null)
				validation = cell12.getStringCellValue();


			if(Debug.CORE) Debug.display(AldoParser.class, "Parsing XLS fait, ligne: "+row.getRowNum());
			/*if(Debug.CORE) Debug.display(AldoParser.class, sens+"\t"+date+"\t"+quantite+"\t"+isin+"\t"+libelleVal+"\t"+numCompte+"\t"+libelleCompte
					+"\t"+donneur+"\t"+service+"\t"+denouement+"\t"+cours+"\t"+courtage+"\t"+validation);*/


			// cloner plutot que lire le fichier xml
			AldoLine aldoLine = cloneLineAldo(aldoLineModel);
			if(Debug.CORE) Debug.display(AldoParser.class, "Clonage de la structure Aldo réussi.");

			// remplir les champs dans la ligne aldo
			fillAldoLine(aldoLine, ++cpt, sens, date, quantite, isin, libelleVal, numCompte, libelleCompte, donneur, service, denouement, cours, courtage, validation);
			if(Debug.CORE) Debug.display(AldoParser.class, "Replissage de la structure Aldo terminée.");

			// creer la ligne
			try {
				createAldoLine(aldoLine);
				if(Debug.CORE) Debug.display(AldoParser.class, "Création de la ligne terminée. [" + (System.currentTimeMillis() - begin) + "ms]\n");
			} catch (AldoException e) {
				e.printStackTrace();
			}
		}

		try {
			file.close();
		} catch (IOException e) {
			e.printStackTrace();
		}


		// ecriture du resultat
		Writer writer = null;
		String aldoFilePath = xlsDirPath + "\\" + GlobalConstant.ALDO_PATH;
		try {
			writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(aldoFilePath), "UTF-8"));
			for(String str : aldoLines)
				writer.write(str+"\r\n"); // specifique a windows, Cariage Return and Line Feed
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try { writer.close(); } catch (Exception e) { e.printStackTrace(); }
		}

		if(Debug.CORE) Debug.display(AldoParser.class, "Création du fichier " + aldoFilePath + " réussi!");
	}

	/**
	 * Creer un clone. Evite de faire des lectures disque à chaque chargement.
	 * 
	 * @param aldo
	 * @return clone
	 */
	public static AldoLine cloneLineAldo(AldoLine aldoLine) {
		AldoLine newLineAldo = new AldoLine();
		newLineAldo.setAldoFields(new ArrayList<AldoField>());

		for(AldoField field : aldoLine.getAldoFields()) {
			AldoField newField = new AldoField();
			newField.setNumber(field.getNumber());
			newField.setName(field.getName());
			newField.setTechName(field.getTechName());
			newField.setMandatory(field.isMandatory());
			newField.setNumerical(field.isNumerical());
			newField.setPrecision(field.getPrecision());
			newField.setPrecisionComa(field.getPrecisionComa());
			newField.setValue(field.getValue());

			newLineAldo.getAldoFields().add(newField);
		}

		return newLineAldo;
	}

	/**
	 * Remplit la structure Aldo à partir des champs passés en paramètres.
	 * Le champs par defauts ainsi que les champs vides ne sont pas touchés.
	 * @param aldo
	 * @param cpt
	 * @param sens
	 * @param date
	 * @param quantite
	 * @param isin
	 * @param libelleVal
	 * @param numCompte
	 * @param libelleCompte
	 * @param donneur
	 * @param service
	 * @param denouement
	 * @param cours
	 * @param courtage
	 * @param validation
	 * @throws Exception
	 */
	public void fillAldoLine(AldoLine aldo, int cpt, String sens, Date date, String quantite, String isin,
			String libelleVal, String numCompte, String libelleCompte, String donneur,
			String service, Date denouement, String cours, String courtage, String validation) {

		List<AldoField> fields = aldo.getAldoFields();

		// 1
		// increment des lignes aldo
		fields.get(0).setValue(String.format("%06d", cpt));

		// 2
		fields.get(1).setValue("E");

		// 3
		Date today = new Date();
		fields.get(2).setValue(dayFormat.format(today));

		// 4
		fields.get(3).setValue(hourFormat.format(today));

		// 5
		// code d'identification unique, id de la société + date + compteur initialisé aléatoirement
		String uniqueId = ID_INVEST + aldo.getValue("DMSG") + String.format("%05d", randomId++);
		fields.get(4).setValue(uniqueId);

		// 10
		fields.get(9).setValue(donneur);

		// 15
		// id de la société
		fields.get(14).setValue("00"+ID_INVEST+"00000");

		// 17
		fields.get(16).setValue(numCompte);

		// 20
		if(sens.equals("A"))
			fields.get(19).setValue(String.valueOf(500));
		else if(sens.equals("V"))
			fields.get(19).setValue(String.valueOf(501));

		// 23
		fields.get(22).setValue(aldo.getValue("DMSG") + aldo.getValue("HMSG"));

		// 24 ?? voir eric

		// 28
		fields.get(27).setValue(sens);

		// 29
		fields.get(28).setValue("C");

		// 30
		fields.get(29).setValue(isin);

		// 31
		fields.get(30).setValue(aldo.getValue("DMSG"));

		// 32
		fields.get(31).setValue(aldo.getValue("HMSG"));

		// 35
		fields.get(34).setValue(quantite);

		// 36
		fields.get(35).setValue(aldo.getValue("DMSG"));

		// 37
		//fields.get(36).setValue(cours);

		// 41 ?? non utilisé

		// 48
		fields.get(47).setValue(aldo.getValue("CRF2OP"));

		// 49 ?? champ trop petit
		// non utilisé

		// 55 ?? voir les règles

		// 56 ??  voir les règles/parel

		// 62 et 63
		if(courtage.equals("")) {
			fields.get(61).setValue("");
			fields.get(62).setValue("");
		}
		else if(courtage.equals("F")) {
			fields.get(61).setValue("F");
			fields.get(62).setValue("");
		}
		else {
			fields.get(61).setValue("G");
			fields.get(62).setValue(courtage);
		}

		// 67
		// non utilisé
		//fields.get(66).setValue(validation);

		// 68
		fields.get(67).setValue(dayFormat.format(date));

		// 72
		fields.get(71).setValue("M");

		// 73
		fields.get(72).setValue(quantite);

		// 76
		fields.get(75).setValue("N");

		// 77
		fields.get(76).setValue("N");

		// 87
		if(denouement != null)
			fields.get(86).setValue(dayFormat.format(denouement));
		else
			fields.get(86).setValue("000000");

		// 126 ?? voir les règles

		// 127 ??  voir les règles

		// 128 ??
		// non utilisé

		// 132 TODO plutard

		// 133 TODO plutard

		// 136
		fields.get(135).setValue(cours);
	}

	/**
	 * Créer une ligne Aldo à partir de la structure passée en paramètre.
	 * Fait aussi les vérifications nécéssaire au formatage de la ligne.
	 * Place la ligne dans un ArrayList pour l'ecrire plutard dans un fichier texte.
	 * 
	 * @param aldo, la structure à partir de la quelle est créee la ligne
	 * 
	 * @throws AldoException si le format n'est pas respecté
	 */
	public void createAldoLine(AldoLine aldo) throws AldoException {
		String line = "";

		int precision = 0;
		int precisionComa = 0;
		String value = "";

		// parcours des champs
		for(AldoField field : aldo.getAldoFields()) {
			precision = field.getPrecision();
			precisionComa = field.getPrecisionComa();
			value = field.getValue();

			// TODO traiter mandatory
			// throw new AldoException("Le champ suivant n'a pas été renseigné :"+field.getName());

			// verifier que le champ est complet et le completer
			// nombres
			if(field.isNumerical()) {
				// nombres a virgule
				if(precisionComa > 0) {
					String[] valueSplit = value.split("\\.");
					//if(field.getNumber() == 37) System.out.println("float: "+value+" "+valueSplit.length);

					String integerValue = "";
					String floatValue = "";

					// cas virgule
					if(valueSplit.length == 2) {
						integerValue = valueSplit[0];
						floatValue = valueSplit[1];
					}
					// cas pas de virgule
					else if(value.length() > 0)
						integerValue = value;
					// sinon cas vide

					//if(field.getNumber() == 35) System.out.println(integerValue + floatValue);

					while(integerValue.length() < precision)
						integerValue = "0" + integerValue;

					while(floatValue.length() < precisionComa)
						floatValue = floatValue + "0";

					value = integerValue + floatValue;
				}
				// nombres entiers
				else
					while(value.length() < precision)
						value = "0" + value;
			}
			// chaines de caracteres
			else
				while(value.length() < field.getPrecision())
					value = value + " ";


			if(value.length() > 0) {
				// verifier la longeur du champ
				if(value.length() != precision + precisionComa)
					throw new AldoException("Le champ suivant n'a pas la bonne taille :" + field.getName()
							+ "\n"+value.length()+" vs " + precision + precisionComa);

				// ajouter la valeur du champ a la ligne aldo
				line += value;
			}
		}

		// verifier la valeur totale de la ligne
		if(line.length() != LINE_LENGHT)
			throw new AldoException("La ligne n'a pas la bonne taille :"+line.length()+" vs "+LINE_LENGHT);

		aldoLines.add(line);
	}

	/**
	 * Redirige la sortie standard vers un fichier de log.
	 */
	public static void redirectOutput() {
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(GlobalConstant.LOG_PATH);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}

		System.setOut(new PrintStream(fos));
		System.setErr(new PrintStream(fos));
	}


	// TODO
	// archiver
	// sur le serveur invest
	// envoi ftp
	public static void main(String[] args) {
		AldoParser.redirectOutput(); // redirige la sortie vers un fichier de logs
		System.out.println("ISTools - Utilitaire de conversion XLS/ALDO\n");

		final SplashScreen splash = SplashScreen.getSplashScreen();
		if (splash == null) {
			System.out.println("SplashScreen.getSplashScreen() returned null");
			return;
		}
		Graphics2D g = splash.createGraphics();
		if (g == null) {
			System.out.println("g is null");
			return;
		}

		long begin = System.currentTimeMillis();

		//AldoLine aldoLineModel = XMLUtil.buildAldoLineModel();
		AldoLine aldoLineModel = XMLUtil.readAldoLineModel(); // charge la structure Aldo

		String xlsPath = args[0];
		AldoParser parser = new AldoParser(xlsPath);
		parser.createLinesFormXLS(aldoLineModel, xlsPath, args[1]);

		/*String xlsPath = "example.xls";
		AldoParser parser = new AldoParser(xlsPath);
		parser.createLinesFormXLS(aldoLineModel, xlsPath, "DonneesAldo");*/

		System.out.println("Temps total: " + (System.currentTimeMillis() - begin)+ " ms");
		splash.close();
	}

}
